//
//  FNMockURLProtocol.m
//  MockServer
//
//  Created by caoyq on 2021/2/10.
//

#import "FNMockURLProtocol.h"
#import "FNMockManager.h"
#import <objc/runtime.h>

static NSString *const kKaleidoPathKey     = @"/kaleido/kaleidoProcess/getResultByKaleido";
static NSString *const kProtocolHandledKey = @"kProtocolHandledKey";

@interface FNMockURLProtocol ()<NSURLSessionDelegate>
 
@property (nonatomic, strong) NSURLSessionDataTask *task;

@end

@implementation FNMockURLProtocol

// 拦截 request 第一步：负责筛选哪些网络请求需要被拦截，返回 YES
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    // 如果是已经拦截过的就放行，避免出现死循环
    if ([NSURLProtocol propertyForKey:kProtocolHandledKey inRequest:request] ) {
       return NO;
    }

    if ([FNMockManager shareInstance].isMock) {
        NSString *path = request.URL.path;
        NSString *value = nil;
        
        // 对于已 mock 的请求，不再拦截
        if ([request.URL.host isEqualToString:[FNMockManager shareInstance].domain]) {
            return NO;
        }
        
        if ([path isEqualToString:kKaleidoPathKey]) {  // kaleido 接口
            NSString *appApiName = [self getApiNameFromRequest:request];
            value = [[FNMockManager shareInstance].mapTable objectForKey:appApiName];
        }else { // 普通接口
            value = [[FNMockManager shareInstance].mapTable objectForKey:path];
        }
        
        if (value) {
            return YES;
        }
    }

    return NO;
}

// 拦截 request 第二步：负责对需要拦截的网络请求（canInitWithRequest 返回 YES）进行重新构造
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    // 设置已处理标志
    [NSURLProtocol setProperty:@(YES)
                        forKey:kProtocolHandledKey
                     inRequest:mutableReqeust];
    return [mutableReqeust copy];
}

// 拦截 request 第三步：自定义拦截 request 的实现
- (void)startLoading {
    NSLog(@"*** FNMockURLProtocol -- startLoading：%@ ***", self.request.URL.absoluteString);
    
    NSURLRequest *mockRequest = nil;
    NSString *value = nil;
    NSString *path = self.request.URL.path;

    if ([path isEqualToString:kKaleidoPathKey]) {  // kaleido 接口
        NSString *appApiName = [FNMockURLProtocol getApiNameFromRequest:self.request];
        value = [[FNMockManager shareInstance].mapTable objectForKey:appApiName];
    }else {  // 普通接口
        value = [[FNMockManager shareInstance].mapTable objectForKey:path];
    }
    if (value) {
        NSString *url = [[FNMockManager shareInstance].domain stringByAppendingPathComponent:value];
        mockRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    }else {
        mockRequest = self.request;
    }
    
    // 请求转发，会进入到 canInitWithRequest 方法中，注意不要被拦截了
    NSURLSessionConfiguration *configuration =
    [NSURLSessionConfiguration defaultSessionConfiguration];
    // 不需要本地的缓存
    configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
                                                          delegate:self
                                                     delegateQueue:[FNMockManager shareInstance].delegateQueue];
    
    self.task = [session dataTaskWithRequest:mockRequest];
    [self.task resume];
}

// 拦截 request 第四步：同样可以实现网络的取消
- (void)stopLoading {
    if (self.task) {
        [self.task cancel];
    }
}

#pragma mark - NSURLSessionDataDelegate & NSURLSessionTaskDelegate

// 第一步：服务器响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    completionHandler(NSURLSessionResponseAllow);
}

// 第二步：获取响应数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
}

// 第三步：完成请求
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    self.task = nil;
    if (!error) {
        [self.client URLProtocolDidFinishLoading:self];
    } else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) {
        
    } else {
        [self.client URLProtocol:self didFailWithError:error];
    }
}

#pragma mark - private

/// 从 request 的 HTTPBodyStream 中获取入参中的 appApiName
+ (NSString *)getApiNameFromRequest: (NSURLRequest *)req {
    NSInteger maxLength = 1024;
    uint8_t d[maxLength];
    NSInputStream *stream = req.HTTPBodyStream;
    NSMutableData *data = [NSMutableData data];
    [stream open];
    BOOL endOfStream = NO;
    while (!endOfStream) {
        NSInteger bytesRead = [stream read:d maxLength:maxLength];
        if (bytesRead == 0) {
            endOfStream = YES;
        }else if (bytesRead == -1) {
            endOfStream = YES;
        }else if (stream.streamError == nil) {
            [data appendBytes:(void *)d length:bytesRead];
        }
    }
    [stream close];
    
    NSDictionary *params = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
    NSString *appApiName = params[@"appApiName"];
    return appApiName;
}

@end
